//
// Created by hugan on 2020/10/16.
//

#ifndef JOYSTICKCPP_JOYSTICK_MANAGER_H
#define JOYSTICKCPP_JOYSTICK_MANAGER_H
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }

#include <iostream>
#include <dinput.h>
#include "JoystickTypeDef.cpp"
#include "JoystickUtil.cpp"
#include "../common/CharsetConverter.h"

class JoystickManager {
private:
    /**
     * DirectInput api
     */
    LPDIRECTINPUT8 g_pDirectInput = nullptr;
    /**
     * 唯一的手柄对象:
     * startListenState时被创建(释放前一个对象)
     * stopListenState时释放对象
     */
    LPDIRECTINPUTDEVICE8 g_pJoystick = nullptr;
    /**
     * 程序运行时的字符串编码:idea开发时是UTF8,window运行时是GBK;没找到cpp中直接获取运行时编码的函数,所以由java传参
     */
    bool g_isUtf8 = false;
    /**
     * 设备状态回调,初始化成功后创建
     */
    func_deviceStateCallback g_deviceStateCallback = nullptr;
    /**
     * 当前是否正在监听,控制停止监听的标志
     */
    bool g_listenState = false;
    /**
     * 唯一的初始化状态
     */
    DIJOYSTATE2 g_emptyState = {};
    /**
     * 上一次发送的非空状态
     */
    DIJOYSTATE2 lastUpdatedState = {};
    /**
     * 上次成功更新状态
     */
    bool lastStateUpdated = false;
public:
    JoystickManager();

    ~JoystickManager();

    void release();

    /**
     * 初始化api
     * https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416756(v=vs.85)
     * @param isUtf8 程序运行时的字符串编码:idea开发时是UTF8,window运行时是GBK;没找到cpp中直接获取运行时编码的函数,所以由java传参
     */
    HRESULT createDirectInput8(bool isUtf8, func_deviceStateCallback deviceStateCallback);

    /**
     * 遍历设备;
     * https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee417804(v=vs.85)
     * @note 没有找到简单方法直接返回EnumDeviceData[],所以在java层实现返回list
     */
    HRESULT enumDevices(func_enumDevicesCallback callback);

    /**
     * 获取在线设备的guid
     * @note 本意是在轮询查询设备时,用来代替enumDevices,减少CPU占用率;
     * .但实际情况是:CPU占用高是DirectInput->EnumDevicesApi的自身问题,即使回调中什么都不处理,也是占用CPU的
     * .且目前无法让java正常收到数据
     */
    vector<string> getOnlineGuid();

    /**
     * 开始监听设备按键;
     * .指令成功时,会阻塞当前线程;
     * .指令执行结果和轮询状态都在 func_deviceStateCallback中返回
     * https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee417897(v=vs.85)
     * @param guidString 来自 CommonUtil::getGUIDString
     * @param stateScanSpan 轮询间隔ms,如1000/n代表每秒最多返回n帧不同的数据
     * @param stickRange 摇杆的返回值范围=±stickRange,如1000
     * @param breakIfErr true-成功轮询监听后,如果出现错误就退出轮询;一般为false,则设备丢失后不用调用重连代码;
     */
    void startListenState(const char *guidString, int stateScanSpan, int stickRange, bool breakIfErr);

    void startListenState(GUID guid, int stateScanSpan, int stickRange, bool breakIfErr);

    /**
     * 停止按键监听
     */
    void stopListenState();

private:
    /**
     * 向java端发送状态;
     * 1.首次错误,发送一次g_emptyState,让java还原状态
     *      Poll,Acquire,GetDeviceState
     * 2.过滤与上次一样状态的事件
     * 3.首次连上,发送一次g_emptyState,丢弃第一次数据!
     *      丢弃原因:监听中的设备关机了,重连后,第一次获取到的是关机前的状态,
     *              原因未明,不排除只是代码bug,也可能是硬件问题
     */
    HRESULT handleJoystickState();

    /**
     * 释放设备对象
     */
    void doFreeDevice();
};

#endif //JOYSTICKCPP_JOYSTICK_MANAGER_H
